-- **************************************************************************
-- * TitanMana.lua
-- *
-- * By: Rothpada
-- * Based on TitanRegen
-- **************************************************************************

-- ******************************** Constants *******************************
local TITAN_MANA_ID = "TitanMana";
local AceTimer = LibStub("AceTimer-3.0"); 

-- ******************************** Variables *******************************
local TM_DamageDone				= { 0 };
local TM_OverhealingDone		= { 0 };
local TM_HealingDone			= { 0 };
local TM_HealingDoneInCombat	= { 0 };
local TM_ManaUsedForDamage		= { 0 };
local TM_ManaUsedForHealing		= { 0 };
	
local TM_IsInCombat				= false;
local TM_CombatTic				= 0;
local TM_TotalTimeInCombat		= 0;
local TM_ManaHistory			= {};
local TM_MH_0					= 0;
local TM_MH_1					= 0;
local TM_MH_2					= 0;
local TM_MH_3					= 0;
local TM_MH_4					= 0;

-- ******************************** Functions *******************************


-- Ref: http://lua-users.org/wiki/SortedIteration

local function __genOrderedIndex( t )
    local orderedIndex = {}
    for key in pairs(t) do
        table.insert( orderedIndex, key )
    end
    table.sort( orderedIndex )
    return orderedIndex
end

local function orderedNext(t, state)
    -- Equivalent of the next function, but returns the keys in the alphabetic
    -- order. We use a temporary ordered key table that is stored in the
    -- table being iterated.

    --print("orderedNext: state = "..tostring(state) )
    if state == nil then
        -- the first time, generate the index
        t.__orderedIndex = __genOrderedIndex( t )
        key = t.__orderedIndex[1]
        return key, t[key]
    end
    -- fetch the next value
    key = nil
    for i = 1,table.getn(t.__orderedIndex) do
        if t.__orderedIndex[i] == state then
            key = t.__orderedIndex[i+1]
        end
    end

    if key then
        return key, t[key]
    end

    -- no more value to return, cleanup
    t.__orderedIndex = nil
    return
end

local function orderedPairs(t)
    -- Equivalent of the pairs() function on tables. Allows to iterate
    -- in order
    return orderedNext, t, nil
end

-- **************************************************************************
-- NAME : TitanPanelTitanManaButton_OnLoad()
-- DESC : Registers the plugin upon it loading
-- **************************************************************************
function TitanPanelTitanManaButton_OnLoad(self)
     self.registry = { 
          id = TITAN_MANA_ID,
          version = TITAN_VERSION,
          menuText = TITAN_MANA_MENU_TEXT, 
          buttonTextFunction = "TitanPanelTitanManaButton_GetButtonText",
          tooltipTitle = TITAN_MANA_ID, 
          tooltipTextFunction = "TitanPanelTitanManaButton_GetTooltipText",
          savedVariables = {
			  IsDebugging = true,
			  ShowHPM = false,
			  ShowDPM = false,
			  ShowETA = true,
			  ShowDPS = false,
			  ShowHPS = false,
			  HistorySize = 10,
          }
     };
     
     self:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED");
     self:RegisterEvent("PLAYER_REGEN_DISABLED");
     self:RegisterEvent("PLAYER_REGEN_ENABLED");
	 self:RegisterEvent("UNIT_MANA");
	 
	 AceTimer.ScheduleRepeatingTimer("TitanPanel"..TITAN_MANA_ID, TitanPanelPluginHandle_OnUpdate, 1, {TITAN_MANA_ID, TITAN_PANEL_UPDATE_BUTTON })

	 TitanMana_output(TITAN_MANA_ID.." by Rothpada (Shadow Council)");
end

-- **************************************************************************
-- NAME : TitanPanelXPButton_OnEvent
-- DESC : Parse events registered to addon and act on them
-- **************************************************************************
function TitanPanelTitanManaButton_OnEvent(self, event, ...)
	local runUpdate = 0;
	local timestamp, etype, sourceGUID, sourceName, sourceFlags, destGUID, destName, destFlags = select(1, ...);
	local combatTime, spellID, spellName, spellSchool, name, rank, icon, cost;
	local isFunnel, powerType, castTime, minRange, maxRange;
	local amount, overkill, school, resisted, blocked, absorbed;
	
	TitanMana_updateManaHistory(time(), UnitPower("player"));
	
	if ( event == "PLAYER_REGEN_DISABLED" ) then
		TM_IsInCombat = true;
		TM_CombatTic = time();
	elseif ( event == "PLAYER_REGEN_ENABLED" ) then
		TM_IsInCombat = false;
		combatTime = time() - TM_CombatTic;
		TM_TotalTimeInCombat = TM_TotalTimeInCombat + combatTime;
		runUpdate = 1;
	end
	
	if ( UnitGUID("player") == sourceGUID and destName ~= nil ) then
		if ( event == "COMBAT_LOG_EVENT" or event == "COMBAT_LOG_EVENT_UNFILTERED" ) then
			if ( destName == nil) then destName = "<nil>"; end
			runUpdate = 1;
			if ( etype == "SPELL_CAST_SUCCESS" or etype == "SPELL_CAST_MISSED" ) then
				-- record mana for instants that do damage or healing
				spellID, spellName, spellSchool = select(9, ...);
				name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = GetSpellInfo(spellID);
				if ( sourceGUID == destGUID ) then
					if ( TM_ManaUsedForHealing[spellID] == nil ) then
						TM_ManaUsedForHealing[spellID] = 0;
					end
					TM_ManaUsedForHealing[spellID] = TM_ManaUsedForHealing[spellID] + cost;
				else
					if ( TM_ManaUsedForDamage[spellID] == nil ) then 
						TM_ManaUsedForDamage[spellID] = 0;
					end 
					TM_ManaUsedForDamage[spellID] = TM_ManaUsedForDamage[spellID] + cost;
				end
			elseif ( etype == "SPELL_DAMAGE" or etype == "SPELL_PERIODIC_DAMAGE" or etype == "SPELL_BUILDING_DAMAGE" ) then
				spellID, spellName, spellSchool = select(9, ...);
				name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = GetSpellInfo(spellID);
				amount, overkill, school, resisted, blocked, absorbed = select(12, ...);
								
				if ( TM_DamageDone[spellID] == nil ) then
					TM_DamageDone[spellID] = 0;
				end
				TM_DamageDone[spellID] = TM_DamageDone[spellID] + amount - overkill;
				-- record mana for spells with a casting time
				if ( etype ~= "SPELL_PERIODIC_DAMAGE" and castTime > 0) then
					if ( TM_ManaUsedForDamage[spellID] == nil ) then
						TM_ManaUsedForDamage[spellID] = 0;
					end
					TM_ManaUsedForDamage[spellID] = TM_ManaUsedForDamage[spellID] + cost;
				end
			elseif ( etype == "SPELL_HEAL" or etype == "SPELL_PERIODIC_HEAL" ) then
				spellID, spellName, spellSchool = select(9, ...);
				name, rank, icon, cost, isFunnel, powerType, castTime, minRange, maxRange = GetSpellInfo(spellID);
				amount, overhealing = select(12, ...);
				if ( TM_HealingDone[spellID] == nil ) then
					TM_HealingDone[spellID] = 0;
				end
				TM_HealingDone[spellID] = TM_HealingDone[spellID] + (amount - overhealing);
				
				if ( TM_OverhealingDone[spellID] == nil ) then
					TM_OverhealingDone[spellID] = 0;
				end
				TM_OverhealingDone[spellID] = TM_OverhealingDone[spellID] + overhealing;

				if ( etype ~= "SPELL_PERIODIC_HEAL" and castTime > 0) then
					-- record mana for spells with a casting time
					if ( TM_ManaUsedForHealing[spellID] == nil ) then
						TM_ManaUsedForHealing[spellID] = 0;
					end
					TM_ManaUsedForHealing[spellID] = TM_ManaUsedForHealing[spellID] + cost;
				end
			elseif ( etype == "SPELL_MISSED" ) then
				--
			else
				TitanMana_debug("etype=="..etype);
			end
		end
	end
	     
	if (runUpdate == 1) then
		TitanPanelPluginHandle_OnUpdate({TITAN_MANA_ID, TITAN_PANEL_UPDATE_ALL})
	end
end

-- **************************************************************************
-- NAME : TitanPanelTitanManaButton_GetButtonText(id)
-- DESC : Calculate regeneration logic for button text
-- VARS : id = button ID
-- **************************************************************************
function TitanPanelTitanManaButton_GetButtonText(id)
	local manaUsedForDamage = TitanMana_getManaUsedForDamage();
	local manaUsedForHealing = TitanMana_getManaUsedForHealing();
	local damageDone = TitanMana_getTotalDamageDone();
	local dpm, hpm, eta;
	local dpmString = "";
	local hpmString = "";
	local etaString = "";
	local dpsString = "";
	local hpsString = "";
	
	if ( TitanGetVar(TITAN_MANA_ID, "ShowDPM") ) then	
		if ( manaUsedForDamage == 0 ) then
			dpm = 0;
		else
			dpm = damageDone / manaUsedForDamage;
		end
		dpmString = format("DPM=%.1f", dpm);
	end
	if ( TitanGetVar(TITAN_MANA_ID, "ShowHPM") ) then	
		if ( manaUsedForHealing == 0 ) then
			hpm = 0;
		else
			hpm = TitanMana_getTotalHealingDone() / manaUsedForHealing;
		end
		hpmString = format("HPM=%.1f", hpm);
	end
	if ( TitanGetVar(TITAN_MANA_ID, "ShowETA") ) then
		TitanMana_updateManaHistory(time(), UnitPower("player"));
		eta = TitanMana_getEstimatedTimeUntilManaDepletedOrFilled();
		if ( eta > 0 ) then
			etaString = "ETA="..TitanUtils_GetGreenText(TitanMana_formatTime(abs(eta)));
		elseif ( eta < 0 ) then
			etaString = "ETA="..TitanUtils_GetRedText(TitanMana_formatTime(abs(eta)));
		else
			etaString = "ETA="..TitanMana_formatTime(abs(eta));
		end
	end
	if ( TitanGetVar(TITAN_MANA_ID, "ShowDPS") ) then
		if ( TM_TotalTimeInCombat > 0 ) then
			dpsString = "DPS="..format("%.0f", damageDone / TM_TotalTimeInCombat);
		else
			dpsString = "DPS=0";
		end
	end
	if ( TitanGetVar(TITAN_MANA_ID, "ShowHPS") ) then
		if ( TM_TotalTimeInCombat > 0 ) then
			hpsString = "HPS="..format("%.0f", TitanMana_getTotalHealingDoneInCombat() / TM_TotalTimeInCombat);
		else
			hpsString = "HPS=0";
		end
	end
	return "Mana: ", format("%s %s %s %s %s", etaString, dpmString, hpmString, dpsString, hpsString);
end

-- **************************************************************************
-- NAME : TitanPanelTitanManaButton_GetTooltipText()
-- DESC : Display tooltip text
-- **************************************************************************
function TitanPanelTitanManaButton_GetTooltipText(self)
	local manaUsedForDamage = TitanMana_getManaUsedForDamage();
	local manaUsedForHealing = TitanMana_getManaUsedForHealing();
	local damageDone = TitanMana_getTotalDamageDone();
	local healingDone = TitanMana_getTotalHealingDone();
	local overhealingDone = TitanMana_getTotalOverhealingDone();
	
	local dpm = damageDone / manaUsedForDamage;
	local hpm = healingDone / manaUsedForHealing;

	local ret = format(
			"Damage Per Mana = %.2f\n"..
			"Healing Per Mana = %.2f\n"..
			"Damage Per Second = %.2f\n"..
			"Healing Per Second = %.2f\n"..
			"Mana Used = %d\n"..
			"Overhealing = %.2f%%\n"..
			"Damage Done = %d\n"..
			"Healing Done = %d\n"..
			"Time in combat = %s",
			dpm, 
			hpm, 
			damageDone / TM_TotalTimeInCombat,
			TitanMana_getTotalHealingDoneInCombat() / TM_TotalTimeInCombat,
			manaUsedForDamage + manaUsedForHealing,
			100 * overhealingDone / (overhealingDone + healingDone),
			damageDone, 
			healingDone,
			TitanMana_formatTime(TM_TotalTimeInCombat)
		); 
	return ret;
end

-- **************************************************************************
-- NAME : TitanPanelRightClickMenu_PrepareTitanManaMenu()
-- DESC : Display rightclick menu options
-- **************************************************************************
function TitanPanelRightClickMenu_PrepareTitanManaMenu()
	local info;
	
	TitanPanelRightClickMenu_AddTitle(TitanPlugins[TITAN_MANA_ID].menuText);
		   
	TitanPanelRightClickMenu_AddToggleVar("Show debug", TITAN_MANA_ID, "IsDebugging");
	TitanPanelRightClickMenu_AddToggleVar("Show healing per mana", TITAN_MANA_ID, "ShowHPM");
	TitanPanelRightClickMenu_AddToggleVar("Show damage per mana", TITAN_MANA_ID, "ShowDPM");
	TitanPanelRightClickMenu_AddToggleVar("Show mana time left", TITAN_MANA_ID, "ShowETA");
	TitanPanelRightClickMenu_AddToggleVar("Show damage per second", TITAN_MANA_ID, "ShowDPS");
	TitanPanelRightClickMenu_AddToggleVar("Show healing per second", TITAN_MANA_ID, "ShowHPS");
	TitanPanelRightClickMenu_AddSpacer();
	TitanPanelRightClickMenu_AddTitle("History Size");
	info = {};
	info.text = "5s";
	info.checked = TitanGetVar(TITAN_MANA_ID,"HistorySize") == 5;
	info.func = function()
		TitanSetVar(TITAN_MANA_ID,"HistorySize", 5);
		TitanPanelButton_UpdateButton(TITAN_MANA_ID);
	end
    UIDropDownMenu_AddButton(info);
	info = {};
	info.text = "10s";
	info.checked = TitanGetVar(TITAN_MANA_ID,"HistorySize") == 10;
	info.func = function()
		TitanSetVar(TITAN_MANA_ID,"HistorySize", 10);
		TitanPanelButton_UpdateButton(TITAN_MANA_ID);
	end
    UIDropDownMenu_AddButton(info);
	info = {};
	info.text = "20s";
	info.checked = TitanGetVar(TITAN_MANA_ID,"HistorySize") == 20;
	info.func = function()
		TitanSetVar(TITAN_MANA_ID,"HistorySize", 20);
		TitanPanelButton_UpdateButton(TITAN_MANA_ID);
	end
    UIDropDownMenu_AddButton(info);
	info = {};
	info.text = "30s";
	info.checked = TitanGetVar(TITAN_MANA_ID,"HistorySize") == 30;
	info.func = function()
		TitanSetVar(TITAN_MANA_ID,"HistorySize", 30);
		TitanPanelButton_UpdateButton(TITAN_MANA_ID);
	end
    UIDropDownMenu_AddButton(info);
	TitanPanelRightClickMenu_AddSpacer();
	info = {};
	info.text = "Reset";
	info.func = function()
		TM_DamageDone = { 0 };
		TM_OverhealingDone = { 0 };
		TM_HealingDone = { 0 };
		TM_HealingDoneInCombat = { 0 };
		TM_ManaUsedForDamage = { 0 };
		TM_ManaUsedForHealing = { 0 };
		TM_TotalTimeInCombat = 0;
		TM_ManaHistory = {};
		TM_MH_0 = 0;
		TM_MH_1 = 0;
		TM_MH_2 = 0;
		TM_MH_3 = 0;
		TM_MH_4 = 0;
		TitanPanelButton_UpdateButton(TITAN_MANA_ID);
	end
    UIDropDownMenu_AddButton(info);
	TitanPanelRightClickMenu_AddSpacer();
	TitanPanelRightClickMenu_AddToggleLabelText(TITAN_MANA_ID);
	TitanPanelRightClickMenu_AddCommand(TITAN_PANEL_MENU_HIDE, TITAN_MANA_ID, TITAN_PANEL_MENU_FUNC_HIDE);      
end

 function TitanMana_debug(msg)
 	if( TitanGetVar(TITAN_MANA_ID,"IsDebugging") ) then 
		DEFAULT_CHAT_FRAME:AddMessage(msg, 1, 1, 1);
	end
 end

 function TitanMana_output(msg)
    DEFAULT_CHAT_FRAME:AddMessage(msg, 1, 1, 1);
 end

function TitanMana_getEstimatedTimeUntilManaDepletedOrFilled()
	local delta_m = 0;
	local ctr = TitanGetVar(TITAN_MANA_ID, "HistorySize");
	local m0 = 0;
	local m1 = nil;
	local den = 0;
	local div;
	local rate;
	
	TM_MH_4 = TM_MH_3;
	TM_MH_3 = TM_MH_2;
	TM_MH_2 = TM_MH_1;
	TM_MH_1 = TM_MH_0;
	
	for index,value in orderedPairs(TM_ManaHistory) do
		if ( m1 == nil ) then
			m1 = value;
		else
			m0 = m1;
			m1 = value;
			if ( ctr < TitanGetVar(TITAN_MANA_ID, "HistorySize") / 2 ) then
				delta_m = delta_m + (m1-m0);
				den = den + 1;
			else
				delta_m = delta_m + (m1-m0)/ctr;
				den = den + 1/ctr;
			end
			ctr = ctr - 1;	
		end
	end	
	
	delta_m = delta_m / den;
	TM_MH_0 = delta_m;
	rate = (TM_MH_0+TM_MH_1+TM_MH_2+TM_MH_3+TM_MH_4)/5;	
	
	-- TitanMana_debug(format("dm=%.2f,mps=%.2f", delta_m, rate));
	
	if ( delta_m == 0 or delta_m == nil or den == 0) then
		return 0;
	elseif ( delta_m > 0 ) then
		 return ( UnitPowerMax("player") - UnitPower("player") ) / rate;
	elseif ( delta_m < 0 ) then
		return UnitPower("player") / rate;
	end	
end

function TitanMana_updateManaHistory(newTime, newMana)
	for index,values in pairs(TM_ManaHistory) do
		if ( newTime - index > TitanGetVar(TITAN_MANA_ID,"HistorySize") ) then
			TM_ManaHistory[index] = nil;
		end
	end
	TM_ManaHistory[newTime] = newMana;
end

function TitanMana_getTotalDamageDone()
	local damageDone = 0;
	for index,value in pairs(TM_DamageDone) do
		damageDone = damageDone + value;
	end
	return damageDone;
end

function TitanMana_getTotalHealingDone()
	local healingDone = 0;
	for index,value in pairs(TM_HealingDone) do
		healingDone = healingDone + value;
	end
	return healingDone;
end

function TitanMana_getTotalHealingDoneInCombat()
	local healingDone = 0;
	for index,value in pairs(TM_HealingDoneInCombat) do
		healingDone = healingDone + value;
	end
	return healingDone;
end

function TitanMana_getTotalOverhealingDone()
	local overhealingDone = 0;
	for index,value in pairs(TM_OverhealingDone) do
		overhealingDone = overhealingDone + value;
	end
	return overhealingDone;
end

function TitanMana_getManaUsedForDamage() 
	local manaUsed = 0;
	for index,value in pairs(TM_ManaUsedForDamage) do
		manaUsed = manaUsed + value;
	end
	return manaUsed;
end

function TitanMana_getManaUsedForHealing() 
	local manaUsed = 0;
	for index,value in pairs(TM_ManaUsedForHealing) do
		manaUsed = manaUsed + value;
	end
	return manaUsed;
end

function TitanMana_formatTime(timeInSeconds) 
	local numMins = floor(timeInSeconds/60);
	local numSecs = timeInSeconds - 60*numMins;
	return format("%dm%ds", numMins, numSecs);
end

function TitanMana_dump() 
	for index,value in pairs(TM_HealingDone) do
		TitanMana_debug("TM_HealingDone["..index.."]="..value);
	end
	for index,value in pairs(TM_ManaUsedForHealing) do
		TitanMana_debug("TM_ManaUsedForHealing["..index.."]="..value);
	end
end

function TitanMana_GetGreenText(msg)
	  local redColorCode = format("%02x", 0);          
	  local greenColorCode = format("%02x", 255);
	  local blueColorCode = format("%02x", 0);          
	  local colorCode = "|cff"..redColorCode..greenColorCode..blueColorCode;
	  return colorCode..text..FONT_COLOR_CODE_CLOSE;
end

function TitanMana_GetRedText(msg)
	  local redColorCode = format("%02x", 255);          
	  local greenColorCode = format("%02x", 0);
	  local blueColorCode = format("%02x", 0);          
	  local colorCode = "|cff"..redColorCode..greenColorCode..blueColorCode;
	  return colorCode..text..FONT_COLOR_CODE_CLOSE;
end

